package com.fred.colorpicker

import android.content.Context
import android.graphics.Color
import android.graphics.LinearGradient
import android.graphics.Paint
import android.graphics.RectF
import android.graphics.Shader
import androidx.annotation.ColorInt
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.unit.IntSize
import com.fred.colorpicker.indicator.RectIndicator
import com.fred.colorpicker.indicator.RingIndicator
import kotlin.math.max
import kotlin.math.min

class GraySeekBarProcessor(context:Context, private val colorChanged: ((rectColor: Int?, ringColor: Int?) -> Unit)? = null) {

    private val x = mutableStateOf(-1f)
    private var size = IntSize.Zero
    private var colorBrightness = 0F
    private var grayBrightness = 0F
    private var colorSaturation = 0F
    private val mHSV by lazy {
        FloatArray(3)
    }

    private val externalHeight = context.dip(5F)
    private val innerHeight = context.dip(4F)
    private val externalRect by lazy {
        RectF()
    }
    private val innerRect by lazy {
        RectF()
    }
    private val externalPaint by lazy {
        Paint(Paint.ANTI_ALIAS_FLAG).apply {
            color = Color.WHITE
            style = Paint.Style.FILL
        }
    }
    private val innerPaint by lazy {
        Paint(Paint.ANTI_ALIAS_FLAG).apply {
            color = Color.BLUE
            style = Paint.Style.FILL
        }
    }

    private val rectIndicator by lazy {
        RectIndicator(context, null).apply {
            isDrawCentre = false
        }
    }
    private val ringIndicator by lazy {
        RingIndicator(context, null).apply {
            isDrawCentre = false
        }
    }
    private var isNeedSelect = false

    fun setSelect(isRect:Boolean, isRing:Boolean,@ColorInt color: Int, hsv:FloatArray) {
        if (isRect) {
            rectIndicator.isSelected = true
            ringIndicator.isSelected = false
        } else if (isRing) {
            rectIndicator.isSelected = false
            ringIndicator.isSelected = true
        }
        val g = rgbToGray(color, hsv[1])
        val colors = intArrayOf(color, g)
        innerPaint.shader = LinearGradient(
            innerRect.left, innerRect.height(), innerRect.right,
            innerRect.height(), colors, null, Shader.TileMode.REPEAT
        )
        val x = if (rectIndicator.isSelected) {
            rectIndicator.getCurrentX()
        } else {
            ringIndicator.getCurrentX()
        }
        if (x == -1f) {
            isNeedSelect = true
        } else {
            isNeedSelect = false
            onChange(Offset(x,0f))
        }

    }

    fun updateState(size: IntSize) {
        if (this.size.width != size.width || this.size.height != size.height) {
            this.size = size
        }
        val centreX:Float = size.height*1F/2
        externalRect.set(0f, centreX - externalHeight,
                         size.width.toFloat(), centreX + externalHeight)
        val d = externalHeight - innerHeight
        innerRect.set(d, centreX - innerHeight,
                      (size.width - d), centreX + innerHeight)

        val centreY = size.height/2F
        rectIndicator.initGrayPosition(size.width, centreY)
        ringIndicator.initGrayPosition(size.width, centreY)
        if (isNeedSelect) {
            val x = if (rectIndicator.isSelected) {
                rectIndicator.getCurrentX()
            } else {
                ringIndicator.getCurrentX()
            }
            onChange(Offset(x,0f))
        }
    }

    fun onPress(position: Offset) {
        onChange(position)
    }

    fun dragStart(position: Offset) {
    }

    fun dragEnd() {
        savePosition()
    }

    fun drag(position: Offset, dragAmount: Offset) {
        onChange(position)
    }

    fun paint(canvas: Canvas) {
        val nativeCanvas = canvas.nativeCanvas
        nativeCanvas.save()
        nativeCanvas.drawRoundRect(externalRect, externalHeight, externalHeight, externalPaint)
        nativeCanvas.drawRoundRect(innerRect, innerHeight, innerHeight, innerPaint)
        if (rectIndicator.isSelected) {
            rectIndicator.draw(nativeCanvas,x.value, -1f)
        } else {
            ringIndicator.draw(nativeCanvas, x.value, -1f)
        }
        nativeCanvas.restore()
    }

    private fun savePosition() {
        if (rectIndicator.isSelected) {
            rectIndicator.saveGrayPosition()
        } else {
            ringIndicator.saveGrayPosition()
        }
    }
    private fun rgbToGray(@ColorInt color: Int, brightness: Float):Int{
        val red = color shr 16 and 0xFF
        val green = (color shr 8) and 0xFF
        val blue = color and 0xFF
        val hsv = FloatArray(3)
        Color.RGBToHSV(red,green,blue,hsv)
        mHSV[0] = hsv[0]
        colorSaturation = hsv[1]
        hsv[1] = 0F
        colorBrightness = hsv[2]
        hsv[2] = brightness
        return Color.HSVToColor(hsv)
    }

    private fun getColor(x:Float):Int{
        val ratio = max(0F,min(x,size.width.toFloat()))/size.width
        mHSV[1] = colorSaturation*(1-ratio)
        mHSV[2] = colorBrightness + (grayBrightness - colorBrightness)*ratio
        return Color.HSVToColor(mHSV)
    }

    private fun onChange(position: Offset) {
        val effectiveX = getEffectiveX(position.x)
        x.value = effectiveX
        if (rectIndicator.isSelected) {
            colorChanged?.invoke(getColor(effectiveX),null)
        } else {
            colorChanged?.invoke(null, getColor(effectiveX))
        }
    }

    private fun getEffectiveX(x: Float):Float {
        val start = 0f
        val dWidth = size.width.toFloat()
        return min(max(x, start), dWidth)
    }

}